package at.ac.thornwishstalon.ocrapp.pic;

import java.io.InputStream;
import java.util.ArrayList;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Paint.Style;
import android.graphics.drawable.ScaleDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnTouchListener;
import android.view.animation.BounceInterpolator;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;


/**
 * 
 * @author fry
 *
 */

public class OCRAPPView extends View{
	private final String TAG="OCRAPP_VIEW";

	static final int NONE = 0;
	static final int DRAG = 1;
	static final int ZOOM = 2;
	static final int CROP_REC=3;

	private int toolmode;
	private PointF last = new PointF();
	private PointF start = new PointF();

	private Context context;
	private Bitmap bitmap;
	private Bitmap viewBitmap;
	private Uri imageUri;

	private ArrayList<Rect> bb;
	private ArrayList<Rect> tl;
	private ArrayList<Rect> re;
	private boolean showBB;
	private boolean showTL;
	private boolean showRE;

	private float scale;

	private int windowHeight; //viewport height
	private int windowWidth; //viewport width
	private int objectWidth; //original width
	private int objectHeight; //original height
	private float ratio;

	private int paddingX; 
	private int paddingY;

	private Matrix viewToWorld = new Matrix();
	private Matrix worldToView= new Matrix();

	private RectF viewBoundary;
	private RectF imageBoundary;
	private RectF selectionRec;

	private int mode;
	private boolean inRecMode=false;
	private boolean rectSet=false;
	private boolean ready=false;
	private OCRAPPcropTool tool;

	private PaintBucket paint= PaintBucket.getInstance();

	private RectF src;
	private RectF dst;


	private ScaleGestureDetector scaleDetector;

	private RectF selectionRecInView;

	public OCRAPPView(Context context, AttributeSet attrs) {
		super(context, attrs);

		this.context=context;

		viewBoundary= new RectF();
		imageBoundary= new RectF();

		src= new RectF();
		dst= new RectF();
		scaleDetector = new ScaleGestureDetector(context, new ScaleListener());


		setOnTouchListener(new OnTouchListener() {
			//#####################################################################################
			//onTouch
			public boolean onTouch(View v, MotionEvent event) {

				scaleDetector.onTouchEvent(event);

				//matrix.getValues(m);
				//float x = m[Matrix.MTRANS_X];
				//float y = m[Matrix.MTRANS_Y];
				PointF curr = new PointF(event.getX(), event.getY());

				if(!inRecMode){/*

					switch (event.getAction()) {
					case MotionEvent.ACTION_DOWN:
						last.set(event.getX(), event.getY());
						start.set(last);
						mode = DRAG;
						break;
					case MotionEvent.ACTION_MOVE:
						if (mode == DRAG) {
							float deltaX = curr.x - last.x;
							float deltaY = curr.y - last.y;
							float scaleWidth = Math.round(origWidth * saveScale);
							float scaleHeight = Math.round(origHeight * saveScale);
							if (scaleWidth < width) {
								deltaX = 0;
								if (y + deltaY > 0)
									deltaY = -y;
								else if (y + deltaY < -bottom)
									deltaY = -(y + bottom); 
							} else if (scaleHeight < height) {
								deltaY = 0;
								if (x + deltaX > 0)
									deltaX = -x;
								else if (x + deltaX < -right)
									deltaX = -(x + right);
							} else {
								if (x + deltaX > 0)
									deltaX = -x;
								else if (x + deltaX < -right)
									deltaX = -(x + right);

								if (y + deltaY > 0)
									deltaY = -y;
								else if (y + deltaY < -bottom)
									deltaY = -(y + bottom);
							}
							matrix.postTranslate(deltaX, deltaY);
							last.set(curr.x, curr.y);
						}
						break;

					case MotionEvent.ACTION_UP:
						mode = NONE;						
						break;

					case MotionEvent.ACTION_POINTER_UP:
						mode = DRAG;
						break;

					case MotionEvent.ACTION_POINTER_DOWN:
						mode=ZOOM;
						break;
					}


					setImageMatrix(matrix);
					invalidate();
				 */
					return true; // indicate event was handled

				}
				else{
					switch(event.getAction() & MotionEvent.ACTION_MASK){
					case MotionEvent.ACTION_DOWN :
						Log.d(TAG, "down");
						mode=DRAG;


						last.set(event.getX(), event.getY());
						start.set(last);


						if(InRecMode()){
							int mod= tool.check(event.getX(), event.getY());
							switch(mod){
							case OCRAPPcropTool.NOT:break;
							case OCRAPPcropTool.INSIDE:
								tool.setMode(OCRAPPcropTool.INSIDE);
								toolmode=OCRAPPcropTool.INSIDE;
								invalidate();
								break;
							case OCRAPPcropTool.TOP:
								//tool.setTop(startY-previousTranslateY);
								tool.setMode(OCRAPPcropTool.TOP);
								toolmode=OCRAPPcropTool.TOP;
								invalidate();
								break;
							case OCRAPPcropTool.LEFT:
								//tool.setLeft(startX-previousTranslateX);
								tool.setMode(OCRAPPcropTool.LEFT);
								toolmode=OCRAPPcropTool.LEFT;
								invalidate();
								break;
							case OCRAPPcropTool.RIGHT:
								//tool.setRight(startX-previousTranslateX);
								tool.setMode(OCRAPPcropTool.RIGHT);
								toolmode=OCRAPPcropTool.RIGHT;
								invalidate();
								break;
							case OCRAPPcropTool.BOTTOM:
								//tool.setBottom(startY-previousTranslateY);
								tool.setMode(OCRAPPcropTool.BOTTOM);
								toolmode=OCRAPPcropTool.BOTTOM;
								invalidate();
								break;


							}
						}
						break;
					case MotionEvent.ACTION_MOVE:
						float deltaX = curr.x - last.x;
						float deltaY = curr.y - last.y;
						//double distance = Math.sqrt(Math.pow(event.getX() - (startX + previousTranslateX), 2) + Math.pow(event.getY() - (startY + previousTranslateY), 2));
						//if(distance > 0) 
						//	dragged=true;

						RectF src= imageBoundary;
						RectF dst= apply(src, centerView());
						//centerView().mapRect(dst, src);

						//scaleMatrix.mapRect(tmp, imageBoundary);


						Log.d(TAG, " move");
						if(InRecMode()){
							Log.d(TAG, "rec move");
							int mod=toolmode;
							switch(mod){
							case OCRAPPcropTool.NOT:break;
							case OCRAPPcropTool.INSIDE:
								//tool.setTop(event.getY()-startY);
								if(true){ // range check TODO
									tool.setMode(OCRAPPcropTool.INSIDE);
									tool.setInside(dst,deltaX,deltaY);
									invalidate();
									break;
								}
							case OCRAPPcropTool.TOP:
								//tool.setTop(event.getY()-startY);
								tool.setTop(deltaY, dst.top);
								tool.setMode(OCRAPPcropTool.TOP);
								invalidate();

								break;
							case OCRAPPcropTool.LEFT:
								//tool.setLeft(event.getX()-startX);
								tool.setLeft(deltaX,dst.left);
								tool.setMode(OCRAPPcropTool.LEFT);
								invalidate();

								break;
							case OCRAPPcropTool.RIGHT:
								//tool.setRight(event.getX()-startX);
								tool.setRight(deltaX,dst.right);
								tool.setMode(OCRAPPcropTool.RIGHT);
								invalidate();

								break;
							case OCRAPPcropTool.BOTTOM:
								//tool.setBottom(event.getY()-startY);
								tool.setBottom(deltaY,dst.bottom);
								tool.setMode(OCRAPPcropTool.BOTTOM);
								invalidate();

								break;
							}
							last.set(curr.x, curr.y);
						}

						break;
					case MotionEvent.ACTION_UP:
						Log.d(TAG, "up");
						mode=NONE;
						//dragged=false;
						if(inRecMode){
							tool.setMode(OCRAPPcropTool.NOT);
							toolmode=OCRAPPcropTool.NOT;
							invalidate();
						}
						break;
					case MotionEvent.ACTION_POINTER_DOWN:
						mode=ZOOM;
						break;
					case MotionEvent.ACTION_POINTER_UP:
						mode=DRAG;

						break;

					}

					selectionRec= tool.getBoundary();

				}
				return true;


			}
		});
	}


	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		Log.d(TAG, "onMEasure");

		windowWidth = MeasureSpec.getSize(widthMeasureSpec);
		windowHeight = MeasureSpec.getSize(heightMeasureSpec);


		Log.d(TAG, "winHeight: "+windowHeight);
		Log.d(TAG, "winWidth: "+windowWidth);
		//Fit to screen.
		float scale;
		float scaleX =  (float)windowWidth / (float)objectWidth;
		float scaleY = (float)windowHeight / (float)objectHeight;

		scale = Math.min(scaleX, scaleY);




		/*
		// Center the image
		redundantYSpace = (float)height - (scale * (float)bmHeight) ;
		redundantXSpace = (float)width - (scale * (float)bmWidth);
		redundantYSpace /= (float)2;
		redundantXSpace /= (float)2;

		matrix.postTranslate(redundantXSpace, redundantYSpace);
		screenToOriginal.preTranslate(-redundantXSpace, -redundantYSpace);

		origWidth = width - 2 * redundantXSpace;
		origHeight = height - 2 * redundantYSpace;
		right = width * saveScale - width - (2 * redundantXSpace * saveScale);
		bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);

		imageBoundary.set(redundantXSpace, redundantYSpace, width-redundantXSpace ,redundantYSpace+height); //image boundary
		 */

	}
	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		// TODO Auto-generated method stub
		super.onLayout(changed, left, top, right, bottom);
		Log.d(TAG, "onlayout");
		ready=true;
		calcBitmap(imageUri);

	}
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		//drawBackground(canvas);

		drawBitmap(canvas);
		if(!inRecMode)
			drawInfo(canvas);
		if(inRecMode)
			drawCropTool(canvas);

	}
	//##############################################
	//draw



	private void drawBitmap(Canvas c){
		if(viewBitmap!=null)
			c.drawBitmap(viewBitmap, centerView(), null);
		if(imageBoundary!=null){
			RectF src= imageBoundary;
			RectF dst= new RectF();
			centerView().mapRect(dst, src);
			c.drawRect(dst, paint.get(Color.BLUE, Paint.Style.STROKE, 70));
		}


	}

	private void drawInfo(Canvas c){
		worldToView=worldToView();
		

		if(re!=null && showRE){
			Log.d("TAG", "draw info");
		
			for(Rect r: re){
				src.set(r);

				src=apply(src, worldToView);
				src=apply(src, centerView());
				
				c.drawRect(src, paint.get(Color.GREEN, Paint.Style.FILL, 50));
			}

		}

		if(tl!=null && showTL){
			for(Rect r: tl){
				src.set(r);

				src=apply(src, worldToView);							
				src=apply(src, centerView());
				
				c.drawRect(src, paint.get(Color.RED, Paint.Style.STROKE, 50));
			}

		}

		
		if(bb!=null && showBB){
			for(Rect r: bb){
				src.set(r);

				src=apply(src, worldToView);
				src=apply(src, centerView());

				c.drawRect(src, paint.get(Color.BLUE, Paint.Style.STROKE, 70));
			}

		}
	}

	private void drawCropTool(Canvas c){
		tool.onDrawCanvas(c);
	}
	//##############################################

	private RectF apply(RectF src, Matrix matrix){
		float[] tmp = new float[]{src.left, src.bottom, src.right, src.top};
		matrix.mapPoints(tmp);
		dst.set(tmp[0], tmp[3], tmp[2], tmp[1]);

		return dst;
	}


	private Matrix worldToView(RectF world, RectF view){
		float x_min = world.left;//0;
		float x_max = world.right;//objectWidth;
		float y_min= world.top;//0;
		float y_max= world.bottom;//objectHeight;

		float u_min= view.left;//0;
		float u_max= view.right;//imageBoundary.right;
		float v_min= view.top;//0;
		float v_max= view.bottom;//imageBoundary.bottom;


		float sx= (u_max-u_min)/(x_max-x_min);
		float sy= (v_max-v_min)/(y_max-y_min);

		Matrix tmp= new Matrix();
		tmp.setTranslate(-x_min, -y_min);
		tmp.preScale(sx, sy);
		tmp.preTranslate(u_min, v_min);

		Log.d(TAG, "worldToView: "+tmp.toString());

		return tmp;
	}

	private Matrix worldToView(){
		float x_min = 0;
		float x_max = objectWidth;
		float y_min=0;
		float y_max= objectHeight;

		float u_min= 0;
		float u_max= imageBoundary.right;
		float v_min= 0;
		float v_max= imageBoundary.bottom;


		float sx= (u_max-u_min)/(x_max-x_min);
		float sy= (v_max-v_min)/(y_max-y_min);

		Matrix tmp= new Matrix();
		tmp.setTranslate(-x_min, -y_min);
		tmp.preScale(sx, sy);
		tmp.preTranslate(u_min, v_min);

		Log.d(TAG, "worldToView: "+tmp.toString());

		return tmp;
	}

	private Matrix viewToWorld(RectF view, RectF world){
		float u_min = world.left;//0;
		float u_max = world.right;//objectWidth;
		float v_min=  world.top;//0;
		float v_max= world.bottom;//objectHeight;

		float x_min=view.left;// 0;
		float x_max= view.right;//imageBoundary.right;
		float y_min=view.top;//0;
		float y_max= view.bottom;//imageBoundary.bottom;


		float sx= (u_max-u_min)/(x_max-x_min);
		float sy= (v_max-v_min)/(y_max-y_min);

		Matrix tmp= new Matrix();
		tmp.setTranslate(-x_min, -y_min);
		tmp.preScale(sx, sy);
		tmp.preTranslate(u_min, v_min);

		Log.d(TAG, "viewToWorld: "+tmp.toString());
		return tmp;
	}
	private Matrix viewToWorld(){
		float u_min = 0;
		float u_max = objectWidth;
		float v_min= 0;
		float v_max= objectHeight;

		float x_min= 0;
		float x_max= imageBoundary.right;
		float y_min=0;
		float y_max= imageBoundary.bottom;


		float sx= (u_max-u_min)/(x_max-x_min);
		float sy= (v_max-v_min)/(y_max-y_min);

		Matrix tmp= new Matrix();
		tmp.setTranslate(-x_min, -y_min);
		tmp.preScale(sx, sy);
		tmp.preTranslate(u_min, v_min);

		Log.d(TAG, "viewToWorld: "+tmp.toString());
		return tmp;
	}


	private Matrix centerView(){
		Matrix center= new Matrix();
		center.setTranslate(paddingX/2, paddingY/2);
		return center;
	}	

	

	private Matrix invertCenterView(){
		Matrix center= new Matrix();
		center.setTranslate(-paddingX/2, -paddingY/2);
		return center;
	}



	private RectF findDrawRec(){
		imageBoundary= new RectF();
		RectF rec= new RectF();
		float h = objectHeight;
		float w = objectWidth;
		ratio=w/h;

		PointF set= fit(w,h);
		set= fit(set.x,set.y);

		imageBoundary.set(0, 0, set.x, set.y);

		scale= objectHeight/set.y;

		Log.d(TAG, "ratio: "+ ratio+" h: "+h+"-> "+set.y+" w: "+w+"->"+set.x);
		rec.set(0, 0, set.x, set.y);

		return rec;
	}



	private PointF fit(float w, float h){
		PointF tmp= new PointF();
		if(w==h){
			tmp.x= Math.min(windowHeight, windowWidth);
			tmp.y= Math.min(windowHeight, windowWidth);
		}else if(w > h){
			tmp.x=windowWidth;
			tmp.y=tmp.x*ratio;
		}else{
			tmp.y=windowHeight;
			tmp.x=tmp.y*ratio;
		}	
		return tmp;
	}



	private void calcBitmap(Uri imageUri){
		Log.d(TAG, "calcBitmap");
		BitmapFactory.Options options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true; // decode only the size of the image file


		try{
			Bitmap bitmap;

			InputStream is = context.getContentResolver().openInputStream(imageUri);
			Bitmap tempBitmap = BitmapFactory.decodeStream(is, null, options);
			is.close();

			is = context.getContentResolver().openInputStream(imageUri);
			bitmap = BitmapFactory.decodeStream(is);
			is.close();

			int height = options.outHeight; // The height of the original file
			int width = options.outWidth; // The width of the original file

			objectHeight=height;
			objectWidth=width;

			Log.i(TAG, "--image.height: " +height);
			Log.i(TAG, "--image.width: " +width);

			RectF tmp= findDrawRec();
			Log.d(TAG, "viewBoundary : "+tmp.toString());

			paddingX=windowWidth - Math.round(tmp.right);
			paddingY= windowHeight - Math.round(tmp.bottom); 

			viewBitmap= Bitmap.createScaledBitmap(bitmap, Math.round(tmp.right), Math.round(tmp.bottom), true);
			Log.d(TAG, "padding X: "+paddingX+" paddingY: "+paddingY);


			invalidate();


		}
		catch(Exception e){
			Log.d(TAG, "error");
			Log.e(TAG, e.getMessage());
		}

	}

	private RectF mapSelection(){
		/*
			RectF tmp= new RectF();
			RectF src= new RectF(this.imageBoundary);
			RectF imageBoundary= new RectF();
			centerView().mapRect(imageBoundary, src);
		 */

		RectF tmp= new RectF(tool.getBoundary());
		tmp= apply(tmp, invertCenterView());
		tmp= apply(tmp, viewToWorld());
		Log.i(TAG, "mapSelection: "+tmp.toString());
		return tmp;
	}



	//#################################################################
	//SCALE
	private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
		//Matrix matrix=new Matrix();

		@Override
		public boolean onScale(ScaleGestureDetector detector) {
			// TODO Auto-generated method stub
			return super.onScale(detector);
		}
	}



	//#############################################################
	//Setter

	public void setShow(boolean bb, boolean re, boolean tl){
		this.showBB=bb;
		this.showRE=re;
		this.showTL=tl;
		invalidate();
	}

	public void setImageUri(Uri imageUri){
		this.imageUri=imageUri;
		if(ready)
			calcBitmap(imageUri);
	}


	public boolean InRecMode() {
		// TODO Auto-generated method stub
		return inRecMode;
	}


	public void setRecMode(boolean b) {
		if(b)
			tool= new OCRAPPcropTool(windowWidth/2, windowHeight/2);
		inRecMode=b;

	}

	public void finalizeRect(){
		rectSet=true;
		selectionRec = mapSelection();
		selectionRecInView= tool.getBoundary();
		Log.i(TAG, "mapped selection REC: "+selectionRec.toString());
	}

	public void discardRect(){
		resetCropTool();
		selectionRec=null;
	}
	public RectF getSelectionRec(){
		return selectionRec;
	}

	public void resetCropTool(){
		tool= new OCRAPPcropTool(windowWidth/2, windowHeight/2);
		selectionRec=null;
	}
	public boolean isRecSet(){
		if(selectionRec==null)
			return false;
		else return true;
	}

	public void setBB(ArrayList<Rect> bb){
		this.bb=bb;
	}

	public void setTL(ArrayList<Rect> tl){
		this.tl=tl;
	}
	public void setRE(ArrayList<Rect> re){
		this.re=re;
	}


	public void setSelection(RectF selection) {
		//tool.setStart(selection);	
	}


}